package com.spzx.payment.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradeWapPayRequest;
import com.alipay.api.response.AlipayTradeWapPayResponse;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.spzx.payment.config.AlipayConfig;
import com.spzx.payment.domain.PaymentInfo;
import com.spzx.payment.service.IAlipayService;
import com.spzx.payment.service.IPaymentInfoService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author: atguigu
 * @create: 2025-02-28 14:39
 */
@Slf4j
@Service
public class AlipayServiceImpl implements IAlipayService {

    @Autowired
    private IPaymentInfoService paymentInfoService;

    @Autowired
    private AlipayClient alipayClient;

    @Autowired
    private AlipayConfig alipayConfig;

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 支付宝支付 1.保存本地交易记录 2.对接支付宝得到支付表达
     *
     * @param orderNo
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String submitAlipay(String orderNo) {
        try {
            // 1.保存本地交易记录
            PaymentInfo paymentInfo = paymentInfoService.savePaymentInfo(orderNo);
            if (paymentInfo == null || !paymentInfo.getPaymentStatus().equals(0)) {
                throw new RuntimeException("交易记录状态有误");
            }
            // TODO 2.对接支付宝得到支付表单
            // 创建请求对象
            AlipayTradeWapPayRequest alipayRequest = new AlipayTradeWapPayRequest();//创建API对应的request
            // 同步回调
            alipayRequest.setReturnUrl(AlipayConfig.return_payment_url);
            // 异步回调
            alipayRequest.setNotifyUrl(alipayConfig.getNotify_payment_url());

            /******必传参数******/
            JSONObject bizContent = new JSONObject();
            //商户订单号，商家自定义，保持唯一性
            bizContent.put("out_trade_no", paymentInfo.getOrderNo());
            //支付金额，最小值0.01元
            //bizContent.put("total_amount", paymentInfo.getAmount());
            bizContent.put("total_amount", paymentInfo.getAmount());

            //订单标题，不可使用特殊符号
            bizContent.put("subject", paymentInfo.getContent());
            /******可选参数******/
            //手机网站支付默认传值QUICK_WAP_WAY
            bizContent.put("product_code", "QUICK_WAP_WAY");

            alipayRequest.setBizContent(bizContent.toString());
            AlipayTradeWapPayResponse response = alipayClient.pageExecute(alipayRequest, "POST");
            return response.getBody(); //调用SDK生成表单;
        } catch (AlipayApiException e) {
            log.error("支付宝支付异常:{}", e.getMessage());
            throw new RuntimeException(e);
        }
    }

    /**
     * 支付宝调用该接口通知商户系统支付结果
     *
     * @param paramsMap
     * @return
     */
    @Override
    public String paySucessNotify(Map<String, String> paramsMap) {
        try {
            log.info("支付宝支付成功回调:{}", paramsMap);
            //1.验证签名 避免出现虚假通知，确保该接口调用方是支付宝/防止数据被篡改
            boolean flag = AlipaySignature.rsaCheckV1(
                    paramsMap,
                    AlipayConfig.alipay_public_key,
                    AlipayConfig.charset,
                    AlipayConfig.sign_type
            );

            //2.从请求参数中获取：商户ID，应用ID是否本本地匹配
            String orderNo = paramsMap.get("out_trade_no");

            //幂等性处理 采用通知参数中通知ID或订单ID作为标识
            String notifyId = paramsMap.get("notify_id");
            String key = "pay:notifyId:" + notifyId;
            Boolean flag1 = redisTemplate.opsForValue().setIfAbsent(key, orderNo, 25, TimeUnit.HOURS);
            if (!flag1) {
                log.info("这比交易支付回调已经正常处理过：{}", orderNo);
                return "success";
            }

            if (!flag) {
                log.error("支付宝支付回调签名验证失败,订单编号：{}", orderNo);
                return "fail";
            }
            String appId = paramsMap.get("app_id");
            if (!AlipayConfig.app_id.equals(appId)) {
                log.error("支付宝支付回调appId验证失败,订单编号：{}", orderNo);
                return "fail";
            }

            String sellerId = paramsMap.get("seller_id");
            if (!"2088721004727077".equals(sellerId)) {
                log.error("支付宝支付回调sellerId验证失败,订单编号：{}", orderNo);
                return "fail";
            }

            //3.获取订单实付金额，跟本地交易记录应付金额匹配
            String totalAmount = paramsMap.get("total_amount");
            LambdaQueryWrapper<PaymentInfo> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(PaymentInfo::getOrderNo, orderNo);
            PaymentInfo paymentInfo = paymentInfoService.getOne(queryWrapper);
            if (paymentInfo == null || paymentInfo.getPaymentStatus().intValue() != 0) {
                log.error("该笔交易已处理过");
                return "fail";
            }
            if (new BigDecimal(totalAmount).compareTo(paymentInfo.getAmount()) != 0) {
                log.error("该笔订单实付金额与交易记录金额不一致，订单编号：{}", orderNo);
                return "fail";
            }
            //4.判断支付结果：成功，修改本地交易记录状态（订单、库存扣减）
            String tradeStatus = paramsMap.get("trade_status");
            if ("TRADE_SUCCESS".equals(tradeStatus)) {
                try {
                    // TODO 修改本地交易记录状态
                    paymentInfoService.updatePaymentInfo(paymentInfo, paramsMap);
                    return "success";
                } catch (Exception e) {
                    //如果业务处理失败，删除掉幂等性key,下次可以重新处理
                    log.error("修改本地交易记录状态失败，删除幂等性key：{}", orderNo);
                    redisTemplate.delete(key);
                    throw new RuntimeException(e);
                }
            }
        } catch (Exception e) {
            log.error("支付宝支付回调异常:{}", e.getMessage());
            throw new RuntimeException(e);
        }
        return null;
    }
}
